﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Kokomo.Environment.Documents;
using Kokomo.Environment.Commanding;
using System.Reflection;
using Kokomo.Environment.Services;
using Kokomo.PeInspector.Reflection.Disassembly;

namespace Kokomo.PeInspector.Reflection
{
	class AssemblyDetailViewModel : TabbedDetailViewModel
	{
		internal AssemblyDetailViewModel(
			Assembly assembly,
			NavigationContext navigationContext,
			UiString heading,
			IMetadataHost host
			)
			: base(navigationContext, null, heading)
		{
			this.Assembly = assembly;
			this.Host = host;

			this.Summary.Commands.Add(new CommandViewModel(
				host.AssemblyListHost.AddFavoriteCommand,
				UiString.FromString(Messages.AddToFavoritesText)
				) { Argument = assembly });
			this.Summary.Commands.Add(new CommandViewModel(
				host.ViewModuleDetailsCommand,
				UiString.FromString(Messages.ViewModuleDetailsText)
				) { Argument = assembly.Location });
			this.Summary.Commands.Add(new CommandViewModel(
				host.ExportAssemblySourceCodeCommand,
				UiString.FromString(Messages.ExportSourceCodeText)
				) { Argument = assembly });

			this.Summary.Details.Add(new SummaryDetailItem(
				UiString.FromString(Messages.DisplayNameLabel),
				UiString.FromString(assembly.FullName)
				));

			AssemblyName name = assembly.GetName();
			byte[] publicKeyData = name.GetPublicKey();

			this.Summary.Details.Add(new SummaryDetailItem(
				UiString.FromString(Messages.ImageRuntimeVersionLabel),
				UiString.FromString(assembly.ImageRuntimeVersion)
				));
			if (publicKeyData != null)
			{
				StringBuilder publicKeyBuilder = new StringBuilder(publicKeyData.Length * 2);
				foreach (var b in publicKeyData)
				{
					publicKeyBuilder.Append(b.ToString("X2"));
				}
				this.Summary.Details.Add(new SummaryDetailItem(
					UiString.FromString(Messages.PublicKeyLabel),
					UiString.FromString(publicKeyBuilder.ToString())
					));
			}
			this.Summary.Details.Add(new SummaryDetailLinkItem(
				UiString.FromString(Messages.PathLabel),
				UiString.FromString(assembly.Location),
				CommonCommands.BrowseToFileCommand,
				assembly.Location
				));

			if (assembly.EntryPoint != null)
			{
				MemberViewInfo viewInfo = MemberViewInfo.GetMemberViewInfo(assembly.EntryPoint);
				this.Summary.Details.Add(new SummaryDetailLinkItem(
					UiString.FromString(Messages.EntryPointLabel),
					UiString.FromString(viewInfo.LongHeading),
					navigationContext.NavigateToCommand,
					assembly.EntryPoint
					));
			}

			IImageProvider imageProvider = AssemblyAnalyzerEditor.MetadataIconProvider;

			this.Tabs.Add(new TabPageViewModel(
				UiString.FromString(Messages.OverviewTitle),
				new DelayLoadViewModel(progress => new AssemblyOverviewViewModel(navigationContext, host.AssemblyListHost.GetAssemblyIndex(assembly))),
				null
				));
			SourceDocumentViewModel assemblyInfoSource = new SourceDocumentViewModel(
				writer => host.BuildSourceDocument(assembly, writer),
				navigationContext,
				AssemblyAnalyzerEditor.CSharpFileType,
				assembly.FullName
				);
			this.Tabs.Add(new TabPageViewModel(
				UiString.FromString(Messages.SourceTitle),
				assemblyInfoSource,
				imageProvider.GetImage(ImageKeys.CSharpCode)
				));
			AssemblyReferencesViewModel references = new AssemblyReferencesViewModel(assembly, navigationContext.NavigateToCommand);
			this.Tabs.Add(new TabPageViewModel(
				UiString.FromString(Messages.ReferencesTitle),
				references,
				imageProvider.GetImage(ImageKeys.AssemblyReferences)
				));
			AssemblyResourcesViewModel resources = new AssemblyResourcesViewModel(assembly, navigationContext.NavigateToCommand);
			this.Tabs.Add(new TabPageViewModel(
				UiString.FromString(Messages.ResourcesTitle),
				resources
				));
			DelayLoadViewModel strings = new DelayLoadViewModel(
				progress => this.BuildAssemblyStringsViewModel(assembly, navigationContext, progress)
				);
			this.Tabs.Add(new TabPageViewModel(
				UiString.FromString(Messages.StringsTitle),
				strings,
				imageProvider.GetImage(ImageKeys.String)
				));
		}

		public Assembly Assembly { get; private set; }
		public IMetadataHost Host { get; private set; }

		private object BuildAssemblyStringsViewModel(
			Assembly assembly,
			NavigationContext navigationContext,
			ProgressInfo progress
			)
		{
			AssemblyIndex index = this.Host.AssemblyListHost.GetAssemblyIndex(assembly);

			progress.Text = UiString.FromString(Messages.ScanningMembersText);

			int fieldCount = index.FieldCount;
			int methodCount = index.MethodBaseCount;
			IEnumerable<MethodBase> methods = index.GetAllMethodBases();
			IEnumerable<FieldInfo> fields = index.GetConstantFields();
			progress.Maximum = methodCount + fieldCount;
			IDictionary<string, StringDependencyGroup> stringGroups = new SortedList<string, StringDependencyGroup>();

			// Check methods
			progress.Text = UiString.FromString(Messages.ScanningMethodsText);
			foreach (var method in methods)
			{
				try
				{
					IEnumerable<MsilDependency> dependencies = this.Host.GetDependenciesOf(method);
					foreach (var dependency in dependencies)
					{
						MsilStringDependency stringDependency = dependency as MsilStringDependency;
						if (stringDependency != null)
						{
							StringDependencyGroup group;
							if (!stringGroups.TryGetValue(stringDependency.GetString(), out group))
							{
								group = new StringDependencyGroup(stringDependency.GetString());
								stringGroups.Add(stringDependency.GetString(), group);
							}
							group.Methods.Add(method);
							break;
						}
					}
				}
				catch
				{
					// Skip this member
				}

				progress.Progress++;
			}

			// Check fields
			progress.Text = UiString.FromString(Messages.ScanningFieldsText);
			foreach (var field in fields)
			{
				if (field.IsLiteral && field.FieldType == typeof(string))
				{
					string value = (string)field.GetRawConstantValue();
					if (value != null)
					{
						StringDependencyGroup group;
						if (!stringGroups.TryGetValue(value, out group))
						{
							group = new StringDependencyGroup(value);
							stringGroups.Add(value, group);
						}
						group.Constants.Add(field);
					}
				}

				progress.Progress++;
			}

			AssemblyStringsViewModel viewModel = new AssemblyStringsViewModel(
				assembly,
				stringGroups,
				this.Host,
				navigationContext.NavigateToCommand
				);

			return viewModel;
		}
	}
}
